/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_conf.h"
#include "Clock.h"
#include "Interrupts.h"
#include "Display.h"
#include "Sound.h"
#include "MotionControl.h"

/* Private definitions -------------------------------------------------------*/
#define __RTC_CLKSRC_VAL          0x00000100
#define __RTC_PERIOD              0x000003E8

/*----------------------------------------------------------------------------
 Define  RTCCLK
 *----------------------------------------------------------------------------*/
#if   ((__RTC_CLKSRC_VAL & 0x00000300) == 0x00000000)
  #define __RTCCLK        (0)
#elif ((__RTC_CLKSRC_VAL & 0x00000300) == 0x00000100)
  #define __RTCCLK        (32768)
#elif ((__RTC_CLKSRC_VAL & 0x00000300) == 0x00000200)
  #define __RTCCLK        (32000)
#elif ((__RTC_CLKSRC_VAL & 0x00000300) == 0x00000300)
  #define __RTCCLK        (__HSE/128)
#endif

/* Private variables ---------------------------------------------------------*/
clock_State volatile _clock_state = CLOCK_Calibrating;
bool volatile _clock_alarmed = FALSE;

/* Private function prototypes -----------------------------------------------*/
static void Clock_OnSecond();
static void Clock_OnAlarm();

/* Exported functions --------------------------------------------------------*/

// Clock_Init()
// ------------
// Initialize the clock module.
void Clock_Init()
{
	// initial state is calibrating servos
	_clock_state = CLOCK_Calibrating;

  /* Enable PWR and BKP clocks */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

  /* Allow access to BKP Domain */
  PWR_BackupAccessCmd(ENABLE);

  /* Reset Backup Domain */
  BKP_DeInit();

  /* Enable LSE */
  RCC_LSEConfig(RCC_LSE_ON);

  /* Wait till LSE is ready */
  while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);

  /* Select LSE as RTC Clock Source */
  RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);

  /* Enable RTC Clock */
  RCC_RTCCLKCmd(ENABLE);
  RTC_WaitForLastTask();

  /* Wait for RTC registers synchronization */
  RTC_WaitForSynchro();

  /* Wait until last write operation on RTC registers has finished */
  RTC_WaitForLastTask();

  /* Set RTC prescaler: set RTC period to 1sec */
  RTC_SetPrescaler(__RTC_PERIOD*__RTCCLK/1000-1); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) */

  /* Wait until last write operation on RTC registers has finished -----------*/
  RTC_WaitForLastTask();

  /* Wait for RTC registers synchronization ----------------------------------*/
  RTC_WaitForSynchro();

  /* Configure one bit for preemption priority */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

  /* Enable the RTC Interrupt ------------------------------------------------*/
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

  /* Configure EXTI Line17(RTC Alarm) to generate an interrupt on rising edge */
  EXTI_DeInit();
  EXTI_ClearITPendingBit(EXTI_Line17);
  EXTI_InitTypeDef EXTI_InitStructure;
  EXTI_InitStructure.EXTI_Line = EXTI_Line17;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);

  /* Enable the RTC Alarm Interrupt ------------------------------------------*/
  NVIC_InitStructure.NVIC_IRQChannel = RTCAlarm_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

  /* Enable the RTC Second ---------------------------------------------------*/
  RTC_ITConfig(RTC_IT_SEC, ENABLE);
  RTC_WaitForLastTask();

  /* Enable the RTC Alarm interrupt ------------------------------------------*/
  RTC_ITConfig(RTC_IT_ALR, ENABLE);
  RTC_WaitForLastTask();

  /* Register the interrupt callbacks -----------------------------------------*/
  Interrupts_RegisterCallback(INTERRUPTS_RTC, Clock_OnSecond);
  Interrupts_RegisterCallback(INTERRUPTS_RTCAlarm, Clock_OnAlarm);
}

// Clock_SetTime()
// ---------------
// Set the time.
extern void Clock_SetTime(uint32_t time)
{
  RTC_WaitForLastTask();
  RTC_SetCounter(time);
  RTC_WaitForLastTask();
  RTC_WaitForSynchro();
}

// Clock_SetAlarm()
// ----------------
// Set the alarm time.
extern void Clock_SetAlarm(uint32_t time)
{
  RTC_WaitForLastTask();
  RTC_SetAlarm(time);
  RTC_WaitForLastTask();
  RTC_WaitForSynchro();
}

// Clock_GetAlarm()
// ----------------
// Get the current alarm time.
uint32_t Clock_GetAlarm()
{
	uint16_t lvalue = RTC->ALRL;
	return (((uint32_t)RTC->ALRH << 16) | lvalue);
}


// Clock_ToggleAlarm()
// -------------------
// Enable/disable the alarm.
extern void Clock_ToggleAlarm()
{
	_clock_alarmed = !_clock_alarmed;

	if (_clock_alarmed)
	{
		Display_EnableAlarm(TRUE);
	}
	else
	{
		Display_EnableAlarm(FALSE);
	}
}

// Clock_SetNextState()
// --------------------
// Set the next state of the clock.
extern void Clock_SetNextState()
{
	switch (_clock_state)
	{
	    //case CLOCK_Calibrating:
	    case CLOCK_Running:
	    	_clock_state = CLOCK_Setting;
			MotionControl_SetState(MOTIONCONTROL_Configuring);
	    	break;

		default:
	    case CLOCK_Setting:
		case CLOCK_Alarm:
	    	_clock_state = CLOCK_Running;
	    	MotionControl_SetState(MOTIONCONTROL_Running);
	    	break;
	}
}

// Clock_GetState()
// ----------------
// Get the current clock state.
clock_State Clock_GetState()
{
	return _clock_state;
}

/* Private functions ---------------------------------------------------------*/

// Clock_OnSecond()
// ----------------
// One second interrupt handler.
static void Clock_OnSecond()
{
	int seconds = RTC_GetCounter();
	int second = seconds % 60;

	// if we are not in the process of setting the time...
	if (_clock_state == CLOCK_Running || _clock_state == CLOCK_Alarm)
	{
		// show the current time
		Display_SetTime(seconds);
	}

    // if the alarm is running play the sound every two seconds
	if (_clock_state == CLOCK_Alarm && (second % 2) == 1)
	{
		Sound_Play();
	}

	// update the robot position
	MotionControl_OnSecond(second);
}

// Clock_OnAlarm()
// ---------------
// Alarm interrupt handler.
static void Clock_OnAlarm()
{
	if (_clock_alarmed && _clock_state == CLOCK_Running)
	{
		_clock_state = CLOCK_Alarm;
		MotionControl_SetState(MOTIONCONTROL_Alarm);
	}
}
